/**
 *    Copyright 2009-2017 the original author or authors.
 *
 *    Licensed under the Apache License, Version 2.0 (the "License");
 *    you may not use this file except in compliance with the License.
 *    You may obtain a copy of the License at
 *
 *       http://www.apache.org/licenses/LICENSE-2.0
 *
 *    Unless required by applicable law or agreed to in writing, software
 *    distributed under the License is distributed on an "AS IS" BASIS,
 *    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *    See the License for the specific language governing permissions and
 *    limitations under the License.
 */
package org.apache.ibatis.parsing;

import java.io.InputStream;
import java.io.Reader;
import java.io.StringReader;
import java.util.ArrayList;
import java.util.List;
import java.util.Properties;

import javax.xml.namespace.QName;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.xpath.XPath;
import javax.xml.xpath.XPathConstants;
import javax.xml.xpath.XPathFactory;

import com.hsh.test.Order;
import org.apache.ibatis.builder.BuilderException;
import org.w3c.dom.Document;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.xml.sax.EntityResolver;
import org.xml.sax.ErrorHandler;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;
import org.xml.sax.SAXParseException;

/**
 * XML的XPath解析器，提供了一些解析XML的方法
 * 四类构造函数：
 * 1、{@link #XPathParser(Document, boolean, Properties, EntityResolver)}
 * 		通过Document、validation、variables、entityResolver构建实例对象，还有三个重载方法:
 * 		{@link #XPathParser(Document)}、{@link #XPathParser(Document, boolean)}、{@link #XPathParser(Document, boolean, Properties)}
 * 2、{@link #XPathParser(InputStream, boolean, Properties, EntityResolver)}
 * 		通过InputStream、validation、variables、entityResolver构建实例对象，还有三个重载方法:
 * 		{@link #XPathParser(InputStream)}、{@link #XPathParser(InputStream, boolean)}、{@link #XPathParser(InputStream, boolean, Properties)}
 * 3、{@link #XPathParser(Reader, boolean, Properties, EntityResolver)}
 * 		通过Reader、validation、variables、entityResolver构建实例对象，还有三个重载方法:
 * 		{@link #XPathParser(Reader)}、{@link #XPathParser(Reader, boolean)}、{@link #XPathParser(Reader, boolean, Properties)}
 * 4、{@link #XPathParser(String, boolean, Properties, EntityResolver)}
 * 		通过String、validation、variables、entityResolver构建实例对象，还有三个重载方法:
 * 		{@link #XPathParser(String)}、{@link #XPathParser(String, boolean)}、{@link #XPathParser(String, boolean, Properties)}
 * @author Clinton Begin
 */
public class XPathParser {
  /**
   * Document对象
   */
  private final Document document;
  /**
   * 是否开启验证
   */
  private boolean validation;
  /**
   * 用于加载本地的DTD文件
   */
  private EntityResolver entityResolver;
  /**
   * 对应配置文件中<propteries>标签定义的键位对集合
   */
  private Properties variables;
  /**
   * XPath对象
   */
  private XPath xpath;

  public XPathParser(String xml) {
    commonConstructor(false, null, null);
    this.document = createDocument(new InputSource(new StringReader(xml)));
  }

  public XPathParser(Reader reader) {
    commonConstructor(false, null, null);
    this.document = createDocument(new InputSource(reader));
  }

  public XPathParser(InputStream inputStream) {
    commonConstructor(false, null, null);
    this.document = createDocument(new InputSource(inputStream));
  }

  public XPathParser(Document document) {
    commonConstructor(false, null, null);
    this.document = document;
  }

  public XPathParser(String xml, boolean validation) {
    commonConstructor(validation, null, null);
    this.document = createDocument(new InputSource(new StringReader(xml)));
  }

  public XPathParser(Reader reader, boolean validation) {
    commonConstructor(validation, null, null);
    this.document = createDocument(new InputSource(reader));
  }

  public XPathParser(InputStream inputStream, boolean validation) {
    commonConstructor(validation, null, null);
    this.document = createDocument(new InputSource(inputStream));
  }

  public XPathParser(Document document, boolean validation) {
    commonConstructor(validation, null, null);
    this.document = document;
  }

  public XPathParser(String xml, boolean validation, Properties variables) {
    commonConstructor(validation, variables, null);
    this.document = createDocument(new InputSource(new StringReader(xml)));
  }

  public XPathParser(Reader reader, boolean validation, Properties variables) {
    commonConstructor(validation, variables, null);
    this.document = createDocument(new InputSource(reader));
  }

  public XPathParser(InputStream inputStream, boolean validation, Properties variables) {
    commonConstructor(validation, variables, null);
    this.document = createDocument(new InputSource(inputStream));
  }

  public XPathParser(Document document, boolean validation, Properties variables) {
    commonConstructor(validation, variables, null);
    this.document = document;
  }

  public XPathParser(String xml, boolean validation, Properties variables, EntityResolver entityResolver) {
    commonConstructor(validation, variables, entityResolver);
    this.document = createDocument(new InputSource(new StringReader(xml)));
  }

  public XPathParser(Reader reader, boolean validation, Properties variables, EntityResolver entityResolver) {
    commonConstructor(validation, variables, entityResolver);
    this.document = createDocument(new InputSource(reader));
  }

  public XPathParser(InputStream inputStream, boolean validation, Properties variables, EntityResolver entityResolver) {
    commonConstructor(validation, variables, entityResolver);
    this.document = createDocument(new InputSource(inputStream));
  }

  public XPathParser(Document document, boolean validation, Properties variables, EntityResolver entityResolver) {
    commonConstructor(validation, variables, entityResolver);
    this.document = document;
  }

  public void setVariables(Properties variables) {
    this.variables = variables;
  }

  public String evalString(String expression) {
    return evalString(document, expression);
  }

  public String evalString(Object root, String expression) {
    String result = (String) evaluate(expression, root, XPathConstants.STRING);
    result = PropertyParser.parse(result, variables);
    return result;
  }

  public Boolean evalBoolean(String expression) {
    return evalBoolean(document, expression);
  }

  public Boolean evalBoolean(Object root, String expression) {
    return (Boolean) evaluate(expression, root, XPathConstants.BOOLEAN);
  }

  public Short evalShort(String expression) {
    return evalShort(document, expression);
  }

  public Short evalShort(Object root, String expression) {
    return Short.valueOf(evalString(root, expression));
  }

  public Integer evalInteger(String expression) {
    return evalInteger(document, expression);
  }

  public Integer evalInteger(Object root, String expression) {
    return Integer.valueOf(evalString(root, expression));
  }

  public Long evalLong(String expression) {
    return evalLong(document, expression);
  }

  public Long evalLong(Object root, String expression) {
    return Long.valueOf(evalString(root, expression));
  }

  public Float evalFloat(String expression) {
    return evalFloat(document, expression);
  }

  public Float evalFloat(Object root, String expression) {
    return Float.valueOf(evalString(root, expression));
  }

  public Double evalDouble(String expression) {
    return evalDouble(document, expression);
  }

  public Double evalDouble(Object root, String expression) {
    return (Double) evaluate(expression, root, XPathConstants.NUMBER);
  }
  /**
   * 根据正则表达式解析NodeList对象，并转换成List<XNode>对象。上下文是document对象。
   * 
   * @param expression 正则表达式
   * @return 
   */
  public List<XNode> evalNodes(String expression) {
    return evalNodes(document, expression);
  }
  /**
   * 根据上下文和正则表达式解析NodeList对象，并转换成List<XNode>对象
   * 
   * @param root 上下文
   * @param expression 正则表达式
   * @return 
   */
  public List<XNode> evalNodes(Object root, String expression) {
    List<XNode> xnodes = new ArrayList<XNode>();
    NodeList nodes = (NodeList) evaluate(expression, root, XPathConstants.NODESET);
    for (int i = 0; i < nodes.getLength(); i++) {
      xnodes.add(new XNode(this, nodes.item(i), variables));
    }
    return xnodes;
  }
  /**
   * 根据正则表达式解析Node对象，并封装成XNode对象。上下文是document对象。
   * 
   * @param expression 正则表达式
   * @return 
   */
  public XNode evalNode(String expression) {
    return evalNode(document, expression);
  }
  /**
   * 根据上下文和正则表达式解析Node对象，并封装成XNode对象
   * 
   * @param root 上下文
   * @param expression 正则表达式
   * @return 
   */
  public XNode evalNode(Object root, String expression) {
    Node node = (Node) evaluate(expression, root, XPathConstants.NODE);
    if (node == null) {
      return null;
    }
    return new XNode(this, node, variables);
  }
  /**
   * 计算指定上下文中的 XPath 表达式并返回指定类型的结果。
   * 
   * @param expression XPath 表达式
   * @param root 开始上下文（例如节点）
   * @param returnType 所需的返回类型，XPathConstants (NUMBER、STRING、BOOLEAN、NODE 或 NODESET) 中定义的某种类型
   * @return
   */
  private Object evaluate(String expression, Object root, QName returnType) {
    try {
    	//计算指定上下文中的 XPath 表达式并返回指定类型的结果。
    	//如果 returnType 不是 XPathConstants (NUMBER、STRING、BOOLEAN、NODE 或 NODESET) 中定义的某种类型，则抛出 IllegalArgumentException。
    	//如果 item 为 null 值，则将使用一个空文档作为上下文。如果 expression 或 returnType 为 null，则抛出 NullPointerException。
    	return xpath.evaluate(expression, root, returnType);
    } catch (Exception e) {
      throw new BuilderException("Error evaluating XPath.  Cause: " + e, e);
    }
  }
  /**
   * 根据输入源创建Document对象,创建步骤：
   * 	1、 创建DocumentBuilderFactory对象。DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
   * 	2、创建DocumentBuilder对象。DocumentBuilder builder = factory.newDocumentBuilder();
   * 	3、解析Document对象。builder.parse(inputSource);
   * @param inputSource
   * @return
   */
  @Order(v="2.1.1" , d="将流读取成Document实例")
  private Document createDocument(InputSource inputSource) {
    // important: this must only be called AFTER common constructor
    try {
    	//创建DocumentBuilderFactory实例对象
      DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
      /**
       * 设置是否启用DTD验证
       */
      factory.setValidating(validation);
      /**
       * 设置是否支持XML名称空间
       */
      factory.setNamespaceAware(false);
      /**
       * 设置解析器是否忽略注释
       */
      factory.setIgnoringComments(true);
      /**
       * 设置必须删除元素内容中的空格（有时也可以称作“可忽略空格”，请参阅 XML Rec 2.10）。
       * 注意，只有在空格直接包含在元素内容中，并且该元素内容是只有一个元素的内容模式时，
       * 才能删除空格（请参阅 XML Rec 3.2.1）。由于依赖于内容模式，因此此设置要求解析器处于验证模式。默认情况下，其值设置为 false。
       */
      factory.setIgnoringElementContentWhitespace(false);
      /**
       * 指定由此代码生成的解析器将把 CDATA 节点转换为 Text 节点，并将其附加到相邻（如果有）的 Text 节点。默认情况下，其值设置为 false。
       */
      factory.setCoalescing(false);
      /**
       * 指定由此代码生成的解析器将扩展实体引用节点。默认情况下，此值设置为 true。
       */
      factory.setExpandEntityReferences(true);
      //创建DocumentBuilder实例对象
      DocumentBuilder builder = factory.newDocumentBuilder();
      //指定使用 EntityResolver 解析要解析的 XML 文档中存在的实体。将其设置为 null 将会导致底层实现使用其自身的默认实现和行为。
      builder.setEntityResolver(entityResolver);
      //指定解析器要使用的 ErrorHandler。将其设置为 null 将会导致底层实现使用其自身的默认实现和行为。
      builder.setErrorHandler(new ErrorHandler() {
        @Override
        public void error(SAXParseException exception) throws SAXException {
          throw exception;
        }

        @Override
        public void fatalError(SAXParseException exception) throws SAXException {
          throw exception;
        }

        @Override
        public void warning(SAXParseException exception) throws SAXException {
        }
      });
      return builder.parse(inputSource);
    } catch (Exception e) {
      throw new BuilderException("Error creating document instance.  Cause: " + e, e);
    }
  }
  /**
   * 构造器通用代码块
   * 	初始化属性：validation、entityResolver、variables、xpath；
   * 	其中，validation、entityResolver、variables三个参数通过参数传递过来；
   * 	xpath属性是通过XPathFactory创建的：
   * 	XPathFactory factory = XPathFactory.newInstance();
   * 	this.xpath = factory.newXPath();
   * @param validation
   * @param variables
   * @param entityResolver
   */
  private void commonConstructor(boolean validation, Properties variables, EntityResolver entityResolver) {
    this.validation = validation;
    this.entityResolver = entityResolver;
    this.variables = variables;
    XPathFactory factory = XPathFactory.newInstance();
    this.xpath = factory.newXPath();
  }

}
